home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7661 / 7661.xpi / components / RILwebDownloader.js < prev    next >
Text File  |  2009-12-09  |  29KB  |  796 lines

  1. /*
  2.  
  3. License: This source code may not be used in other applications whether they
  4. be personal, commercial, free, or paid without written permission from Read It Later.
  5.  
  6.  
  7. /////////
  8. DEVELOPER API: readitlaterlist.com/api/
  9. /////////
  10.  
  11. If you would like to customize Read It Later or build an application that works with
  12. Read it Later take a look at the READ IT LATER OPEN API:
  13. http://readitlaterlist.com/api/
  14.  
  15. Suggestions for additions to Read It Later are VERY welcome.  A large number of user
  16. suggestions have been implemented.  Please let me know of any additional features you
  17. are seeking at: http://readitlaterlist.com/support/
  18.  
  19. Thanks
  20.  
  21. */
  22.  
  23. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  24.  
  25. function RILwebDownloader() {
  26.     this.type           = 2;
  27.     this.timeout        = 30 * 1000; //
  28.     this.maxActiveRequests = 4; // increasing this will speed up downloading but will reduce Firefox performance    
  29.     this.maxImages      = 300;
  30.     this.maxStylesheets = 30;
  31.     
  32.     this.requests = [];
  33.     this.activeRequests     = 0;
  34.     this.assetQueueCount   = 0;
  35.     this.imageCount         = 0;
  36.     this.stylesheetCount    = 0;
  37.     
  38.     this.threads = [];
  39.     this.imagesQueue = [];
  40.     this.stylesheetQueue = [];
  41.     this.dupeCheckAbsolute = {};   
  42.         
  43.     this.retainDomains = {};         
  44. }
  45.  
  46. // class definition
  47. RILwebDownloader.prototype = {
  48.  
  49.   // properties required for XPCOM registration:
  50.   classDescription: "Read It Later Web Page Downloader Javascript XPCOM Component",
  51.   classID:          Components.ID("{c570f4f0-aaf4-11de-8a39-0800200c9a66}"),
  52.   contractID:       "@ril.ideashower.com/rilwebdownloader;1",
  53.  
  54.   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIRILwebDownloader]),
  55.  
  56.   //////////////////////////////////////////    
  57.     
  58.     // setup
  59.     
  60.     dealloc : function()
  61.     {
  62.         this.APP = null; 
  63.         this.ASSETS = null;
  64.         this.JSON = null;
  65.         this.markup = null;
  66.         this.markupPath = null; 
  67.         this.requests = null;
  68.         this.threads = null;
  69.         this.imagesQueue = null;
  70.         this.stylesheetQueue = null;
  71.         this.dupeCheckAbsolute = null;
  72.         this.retainDomains = null;
  73.     },
  74.     
  75.     init : function(itemId, url)
  76.     {                
  77.         this.itemId = itemId;
  78.         this.url = url;
  79.         
  80.         this.APP    = Components.classes['@ril.ideashower.com/rildelegate;1'].getService().wrappedJSObject;        
  81.         this.ASSETS = Components.classes['@ril.ideashower.com/rilassetmanager;1'].createInstance(Components.interfaces.nsIRILassetManager);
  82.         this.ASSETS.init();
  83.         this.JSON   = Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON);
  84.         this.markupPath = this.ASSETS.folderPathForItemId( itemId ) + 'web.html';
  85.         
  86.         this.processorPrototype();
  87.         this.fileDownloaderPrototype();
  88.     },
  89.     
  90.     start : function(threadId)
  91.     {
  92.         //this.APP.d('start downloading ' + this.url);
  93.         this.threadId = threadId;
  94.         this.request(this.url, false, this, this.markupCallback);       
  95.     },
  96.     
  97.     
  98.  
  99.     // Handling file and assets requests
  100.     
  101.     request : function(url, isBinary, delegate, callback, itemInfo, wait)
  102.     {
  103.         //this.APP.d('request');
  104.         let request = new this.fileDownloader(url, isBinary, delegate, callback);
  105.         request.itemInfo = itemInfo;
  106.         this.requests.push(request);
  107.         if (!wait) this.popRequest();
  108.         return request;
  109.     },
  110.     
  111.     popRequest : function()
  112.     {
  113.         if (this.requests && this.activeRequests <= this.maxActiveRequests && this.requests.length > 0 && !this.finished)
  114.         {
  115.             this.activeRequests++;
  116.             let request = this.requests.shift();
  117.             if (request) request.start();
  118.         }
  119.     },
  120.     
  121.     requestAsset : function(data)
  122.     {
  123.         //this.APP.d('requestAsset');
  124.         let itemInfo = data.itemInfo;
  125.         let type = data.type;
  126.                 
  127.         // If still under max asset caps (should this be a byte level cap rather than #)?
  128.         if ( (type == 1 && this.imageCount < this.maxImages) || (type == 2 && this.stylesheetCount < this.maxStylesheets) ) {
  129.         
  130.             // Make sure the asset doesn't already exist and then begin downloading
  131.             // they don't download at same time
  132.             if ( !this.dupeCheckAbsolute[ itemInfo.absolute ] && !itemInfo.assetExists )
  133.             //if ( !this.dupeCheckAbsolute[ itemInfo.absolute ] ) //for testing to skip exists check
  134.             {
  135.                 
  136.                 let request = this.request(itemInfo.absolute, (type==1), this, type==1?this.imageAssetFinished:this.stylesheetAssetFinished, itemInfo, true);
  137.             
  138.                 // Add to queue
  139.                 if (type == 1)
  140.                 {                    
  141.                     this.imagesQueue.push(request);
  142.                     this.imageCount++;
  143.                 }
  144.                 else if (type == 2)
  145.                 {
  146.                     this.stylesheetQueue.push(request);
  147.                     this.stylesheetCount++;
  148.                 }
  149.                 this.assetQueueCount++;
  150.         
  151.                 //dump("\n " + this.stylesheetCount + ' + ' + this.imageCount + ' = ' + this.assetQueueCount);
  152.                 
  153.                 // Start the connection            
  154.                 this.popRequest();
  155.                 
  156.                 // Add to checks
  157.                 this.dupeCheckAbsolute[ itemInfo.absolute ] = true;  
  158.                 
  159.             }
  160.         }
  161.                             
  162.         // Log for retain count regardless if we opened the connection here
  163.         this.addRetainDomain( itemInfo.assetDomain );        
  164.         
  165.     },
  166.     
  167.     requestFinished : function()
  168.     {
  169.         this.activeRequests--;
  170.         this.popRequest();
  171.     },
  172.     
  173.     
  174.     // threading
  175.     
  176.     runOnThread : function( func )
  177.     {        
  178.         func.defineMainThreadCallbackPrototype();
  179.         let thread = Components.classes["@mozilla.org/thread-manager;1"].getService().newThread(0);
  180.         thread.dispatch( func , thread.DISPATCH_NORMAL);
  181.         this.threads.push(thread);
  182.     }, 
  183.     
  184.     // process
  185.         
  186.     markupCallback : function(downloader)
  187.     {
  188.       //  this.APP.d('markupCallback ' + this.url);
  189.         this.requestFinished();
  190.         if (!downloader.success) return this.finish(false);
  191.         
  192.         this.imagesProcessed = false;
  193.         this.stylesheetsProcessed = false;
  194.         this.runOnThread( new this.processor('processMarkup', {markup:downloader.data,url:this.url}, this) );
  195.         
  196.     },
  197.     
  198.     markupProcessed : function(markup)
  199.     {
  200.         //this.APP.d('markupProcessed ' + this.url);
  201.         this.imagesProcessed = true;
  202.         this.stylesheetsProcessed = true;
  203.         this.markup = markup;
  204.         this.checkIfFinished();        
  205.     },
  206.     
  207.     
  208.     // asset call
  209.     
  210.     
  211.     
  212.     // Processor Thread
  213.     // Handles markup and spawns asset downloads
  214.     
  215.     processor : function(action, vars, delegate)
  216.     {   
  217.        // dump("\nprocessor");
  218.         this.action = action;
  219.         this.delegate = delegate; // is this safe as long as the thread doesn't touch it, just passes it? - otherwise send an id that corresponds to the webDownloader and save it to the OFFLINE object
  220.         for(let i in vars)
  221.         {
  222.             this[i] = vars[i];
  223.         }
  224.         
  225.         // Because only one specific item of content is ever processed at a time, we dropped the need to do contentId lookups with this
  226.         //this.dupeCheckAbsolute = {}; // absolute is handled in the scope of RILwebDownloader
  227.         this.dupeCheckLiteral = {}; // literals are handled in the scope per file (per processor thread)
  228.         
  229.     },
  230.     
  231.     processorPrototype : function()
  232.     { // RUN IN THREAD        
  233.         
  234.         this.processor.prototype = {
  235.             
  236.             run : function()
  237.             {
  238.                 //dump("\nprocessor " + this.url);
  239.                 this.init();
  240.                 
  241.                 switch(this.action)
  242.                 {
  243.                     case('processMarkup'):
  244.                         this.processMarkup();
  245.                         break;
  246.                     
  247.                     case('processStylesheet'):
  248.                         this.processStylesheet();
  249.                         break;
  250.                     
  251.                 }
  252.             },
  253.             
  254.             init : function()
  255.             {               
  256.                 this.ASSETS = Components.classes['@ril.ideashower.com/rilassetmanager;1'].createInstance(Components.interfaces.nsIRILassetManager);
  257.                 this.ASSETS.init();
  258.                 this.JSON   = Components.classes["@mozilla.org/dom/json;1"].createInstance(Components.interfaces.nsIJSON);
  259.             },
  260.             
  261.             processMarkup : function()
  262.             {               
  263.                 //dump("\nprocessMarkup");         
  264.                 // Begin Scan for images
  265.                 this.markup = this.processImages( this.markup, 'markup', 1);        
  266.                 this.markup = this.processImages( this.markup, 'markup', 2 );
  267.                 
  268.                 // Begin Scan for stylesheets
  269.                 this.markup = this.processStylesheets( this.markup, 'markup', 1);        
  270.                 this.markup = this.processStylesheets( this.markup, 'markup', 2);
  271.                 
  272.                 // Replace relative links
  273.                 this.markup = this.processLinks( this.markup );
  274.                 
  275.                 this.runOnMain('markupProcessed', this.markup);
  276.             },
  277.             
  278.             processStylesheet : function()
  279.             {
  280.                 //dump("\processStylesheet");                
  281.                 this.markup = this.processStylesheets(this.markup, this.url, 2, this.itemInfo);
  282.                 this.markup = this.processImages(this.markup, this.url, 2, this.itemInfo);
  283.                 
  284.                 // Strip any remaining items with absolute urls
  285.                 this.markup = this.markup.replace(/(['"\(])?https?:\/\//gi, '$1/UNREPLACEDABSOLUTE/');
  286.                 
  287.                 this.runOnMain('stylesheetProcessed', {markup:this.markup, itemInfo:this.itemInfo});                
  288.             },
  289.             
  290.             processImages : function( content, contentId, type, itemInfo )
  291.             {
  292.                 //dump("\processImages");
  293.                 this.imagesProcessed = false;
  294.                 
  295.                 let literal, match, matchSplit;    
  296.                 let regex = type == 1 ? /<(\s)?(img|input) ([^>]*)?src=["']([^"']*)["']/gi : /background(-image)?:[^;}\(]*url\(['"]?([^'"\(\)]*)['"]?\)/gi;
  297.                 
  298.                 // Take markup and scan for img tags                
  299.                 match = regex.exec(content);
  300.                 while(match)
  301.                 {
  302.                     literal = match[ type==1 ? 4 : 2 ];
  303.                     content = this.processAsset(literal, content, contentId, 1, itemInfo ? itemInfo.absolute : null);
  304.                     
  305.                     // Next match
  306.                     match  = regex.exec(content);
  307.                 }
  308.                 
  309.                 this.imagesProcessed = true;
  310.                 
  311.                 return content;   
  312.             
  313.             },
  314.             
  315.             processStylesheets : function(content, contentId, type, itemInfo)
  316.             {
  317.                 //dump("\processStylesheets");
  318.                 
  319.                 this.stylesheetsProcessed = false;
  320.                 
  321.                 if (content) {
  322.                     
  323.                     let literal, match, matchSplit, capture1, capture2, processIt;    
  324.                     let regex = type == 1 ? /<(\s)?link ([^>]*)?href=["']([^"']*)["']([^>]*)?/gi : /@import\s*(url\()?['"]?([^'"\(\)]*)['"]?/gi;
  325.                     
  326.                     
  327.                     // Take markup and scan for css links and imports                
  328.                     match = regex.exec(content);
  329.                     while(match)
  330.                     {
  331.                         processIt = false;
  332.                         
  333.                         if (type == 1)
  334.                         {
  335.                             literal = match[ 3 ];
  336.                             if (literal)
  337.                             {
  338.                                 // There is an href, now check if it has a rel="stylesheet" before or after it
  339.                                 capture1 = match[2];
  340.                                 capture2 = match[4];
  341.                                 
  342.                                 if ( (capture1 && capture1.match(/stylesheet/i)) || (capture2 && capture2.match(/stylesheet/))) processIt = true;
  343.                             }
  344.                             
  345.                         } else {
  346.                             literal = match[2];
  347.                             if (literal) processIt = true;
  348.                         }
  349.                         
  350.                         if (processIt) content = this.processAsset(literal, content, contentId, 2, itemInfo ? itemInfo.absolute : null);
  351.                         
  352.                         // Next match
  353.                         match  = regex.exec(content);
  354.                     }
  355.                     
  356.                     this.stylesheetsProcessed = true;
  357.                 }
  358.                 
  359.                 return content;
  360.             },
  361.             
  362.             processLinks : function(content)
  363.             {
  364.                 //dump("\processLinks");
  365.                 
  366.                 if (content) {
  367.                     
  368.                     try {
  369.                         let literal, match, absolute;    
  370.                         let regex = /<(\s)?a ([^>]*)?href=["']([^"']*)["']([^>]*)?/gi;
  371.                         let searchContent = content;
  372.                         
  373.                         // Take markup and scan for links
  374.                         match = regex.exec(searchContent);
  375.                         while(match)
  376.                         {
  377.                             literal = match[ 3 ];
  378.                             if (literal.length && !literal.match(/^(\#|https?:\/)/i)) 
  379.                             {
  380.                                 absolute = this.ASSETS.getAbsoluteFromRelative( literal, this.url );
  381.                                 if (absolute && absolute != literal)
  382.                                 {
  383.                                     content = content.replace( literal , absolute );
  384.                                 }
  385.                             }
  386.                             
  387.                             // Next match
  388.                             match  = regex.exec(searchContent);
  389.                         }
  390.                     } catch(e)
  391.                     {
  392.                         content = searchContent;
  393.                     }
  394.                 }
  395.                 
  396.                 return content;
  397.             },
  398.             
  399.             processAsset : function(literal, content, contentId, type, baseURL)
  400.             {
  401.                 //dump("\processAsset");
  402.                 try {
  403.                     
  404.                     // Check to make sure it's not an asset path (already processed)
  405.                     if (!literal || !literal.match(/\S/) || literal.match('RIL_assets')) return content; 
  406.                     
  407.                     
  408.                     // Get a path set for literal
  409.                     let itemInfoJSON = this.ASSETS.pathsForLiteral( literal , !baseURL ? this.url : baseURL, baseURL!=null, type==2?2:false);
  410.                     let itemInfo = itemInfoJSON ? this.JSON.decode(itemInfoJSON) : null;
  411.                     if (!itemInfo) return content;
  412.                     
  413.                     
  414.                     // -- Replace literals with absolutes
  415.                     
  416.                     // dupes should be detected on a specific content basis, not across all files
  417.                     // we removed the need for this.dupeCheckLiteral[contentId] because only one thread is ever processing a specific file
  418.                     
  419.                     if ( !this.dupeCheckLiteral[ itemInfo.literal] ) {
  420.                      
  421.                         // Replace instances of literal in markup with relative paths
  422.                         content = content.replace( itemInfo.literal , itemInfo.assetRelativePath);
  423.                         
  424.                         // Add to dupe array
  425.                         this.dupeCheckLiteral[ itemInfo.literal ] = true;
  426.                             
  427.                     }
  428.                                         
  429.                     this.runOnMain( 'requestAsset', {itemInfo:itemInfo, type:type} );
  430.                 
  431.                 } catch(e) { Components.utils.reportError(e); }
  432.                     
  433.                 return content;
  434.                 
  435.             },
  436.             
  437.             //
  438.             
  439.             QueryInterface : function(iid)
  440.             {
  441.                 if (iid.equals(Components.interfaces.nsIRunnable) ||
  442.                     iid.equals(Components.interfaces.nsISupports)) {
  443.                         return this;
  444.                 }
  445.                 throw Components.results.NS_ERROR_NO_INTERFACE;
  446.             },
  447.             
  448.             runOnMain : function(selector, argument)
  449.             {
  450.                 let main = Components.classes["@mozilla.org/thread-manager;1"].getService().mainThread;
  451.                 main.dispatch(new this.mainThreadCallback(this.delegate, selector, argument), this.DISPATCH_NORMAL);        
  452.             },
  453.             
  454.             mainThreadCallback : function(delegate, selector, argument)
  455.             {
  456.                 this.delegate = delegate;
  457.                 this.selector = selector;
  458.                 this.argument = argument;
  459.             },
  460.                 
  461.             defineMainThreadCallbackPrototype : function()
  462.             {
  463.                 this.mainThreadCallback.prototype = {
  464.                 
  465.                     run: function() {
  466.                         try {
  467.                                                         
  468.                             this.delegate[this.selector].call(this.delegate, this.argument);
  469.                             
  470.                         } catch(err) {
  471.                             Components.utils.reportError(err);
  472.                         }
  473.                     },
  474.                     
  475.                     QueryInterface: function(iid) {
  476.                         if (iid.equals(Components.interfaces.nsIRunnable) ||
  477.                             iid.equals(Components.interfaces.nsISupports)) {
  478.                             return this;
  479.                         }
  480.                         throw Components.results.NS_ERROR_NO_INTERFACE;
  481.                     }
  482.                 };
  483.             }
  484.             
  485.         };
  486.         
  487.     },    
  488.     
  489.     imageAssetFinished : function( downloader )
  490.     {
  491.         if (this.finished) return;
  492.         this.requestFinished();
  493.         
  494.         try
  495.         {
  496.             if (downloader.success && downloader.data)
  497.             {                
  498.                 // Save the image to a file - no delegate callback, if it works it works
  499.                 this.APP.OFFLINE.write(downloader.itemInfo.assetPath, downloader.data , true);
  500.                         
  501.                 
  502.             }
  503.             
  504.         } catch(e) { Components.utils.reportError(e); }
  505.         
  506.         
  507.         downloader = null;
  508.         this.assetFinished();
  509.         
  510.     },
  511.     
  512.     stylesheetAssetFinished : function( downloader )
  513.     {
  514.         if (this.finished) return;
  515.                 //this.APP.d('stylesheetAssetFinished ' + this.url);
  516.         this.requestFinished();
  517.         
  518.         try {
  519.             
  520.             if (downloader.success && downloader.data)
  521.             {
  522.                      
  523.                 this.imagesProcessed = false;
  524.                 this.stylesheetsProcessed = false;
  525.                 
  526.                 // Process the css file
  527.                 this.runOnThread( new this.processor('processStylesheet', {
  528.                     markup:downloader.data,
  529.                     url:downloader.itemInfo.absolute,
  530.                     itemInfo:downloader.itemInfo
  531.                 }, this) );
  532.                         
  533.             }
  534.             
  535.         } catch(e) {
  536.             Components.utils.reportError(e);
  537.             return;
  538.         }
  539.         
  540.         downloader = null;
  541.         this.assetFinished();
  542.         
  543.     },
  544.     
  545.     stylesheetProcessed : function(data)
  546.     {
  547.         if (this.finished) return;
  548.                 //this.APP.d('stylesheetProcessed ' + this.url);
  549.         this.imagesProcessed = true;
  550.         this.stylesheetsProcessed = true;
  551.         
  552.         // Save the css to a file - no delegate callback, if it works it works
  553.         this.APP.OFFLINE.write( data.itemInfo.assetPath, data.markup, true ); 
  554.         this.assetFinished();       
  555.     },
  556.    
  557.     assetFinished : function()
  558.     {
  559.                 //this.APP.d('assetFinished ' + this.url);
  560.         this.assetQueueCount--;
  561.         this.checkIfFinished();
  562.     },
  563.     
  564.     checkIfFinished : function(force)
  565.     {
  566.         //this.APP.d('checkIfFinished ' + this.assetQueueCount + ' | ' + this.imagesProcessed + ' | ' + this.stylesheetsProcessed);
  567.         
  568.         if (this.finished || this.finishing) return true;
  569.         
  570.         if (force || (this.assetQueueCount == 0 &&
  571.             this.imagesProcessed &&
  572.             this.stylesheetsProcessed))
  573.         {
  574.             this.finishing = true;
  575.             //this.APP.d('finishing');
  576.             
  577.             // Do remaining markup cleanup - strip absolutes
  578.             if (this.markup)
  579.                 this.markup = this.markup.replace(/([\s"'])(background|src)=["']https?:([^"']*)["']/gi, '$1$2=""');
  580.             
  581.             // Add content type if it isn't set
  582.             if (this.markup && !this.markup.match(/http-equiv="content-type/i))
  583.                 this.markup += '<meta http-equiv="content-type" content="text/html; charset=UTF-8">';
  584.             
  585.             // Save markup to file
  586.             this.APP.OFFLINE.write( this.markupPath, this.markup, false, this, 'finish');
  587.             
  588.             return true;
  589.         }
  590.         
  591.         // reset timeout
  592.         if (this.timeoutTO)
  593.             this.timeoutTO = this.APP.clearTimeout( this.timeoutTO );
  594.         this.timeoutTO = this.APP.setTimeout(this.timedOut, this.timeout, this, false, this.timeoutTO);        
  595.         
  596.         return false;
  597.         
  598.     },
  599.     
  600.     finish : function(success, statusCode)
  601.     {
  602.         //this.APP.d('finish? ' + this.url);
  603.         if (this.finished) return;
  604.         
  605.         if (this.timeoutTO)
  606.             this.timeoutTO = this.APP.clearTimeout( this.timeoutTO );
  607.             
  608.         this.finished = true;
  609.         this.success = success;
  610.         this.statusCode = statusCode ? statusCode : success ? 1 : -1;
  611.         this.APP.OFFLINE.itemIsDone(this.itemId, this.type, this.threadId, this.success, this.statusCode, this.retainDomains);
  612.         this.shutdownThreads();
  613.         
  614.         this.dealloc();
  615.     },
  616.     
  617.     timedOut : function()
  618.     {
  619.         //dump("\n -- timing out.. " + this.assetQueueCount);
  620.         if (this.finished) return false;
  621.         
  622.         this.cancel(true);
  623.         
  624.         // decide if we should return an error or just skip waiting assets
  625.         if (!this.imagesProcessed || !this.stylesheetsProcessed)
  626.         {
  627.             this.finish(false);
  628.         }
  629.         else
  630.         {
  631.             this.checkIfFinished(true); // force it to finish 
  632.         }        
  633.     },
  634.     
  635.     cancel : function(soft)
  636.     {
  637.         //dump("\n -- cancelling.. " + this.assetQueueCount);
  638.         
  639.         this.APP.clearTimeout( this.timeoutTO );
  640.         
  641.         this.shutdownThreads();
  642.         
  643.         if (!soft)
  644.         {
  645.             this.finished = true;
  646.         }
  647.         
  648.     },
  649.     
  650.     shutdownThreads : function()
  651.     {
  652.         try {
  653.             for(let i in this.threads)
  654.             {
  655.                 if (this.threads[i])
  656.                 {
  657.                     this.threads[i].shutdown();
  658.                     this.threads[i] = null;
  659.                 }
  660.             }
  661.             this.threads = null;
  662.             this.threads = [];
  663.         } catch(e) { Components.utils.reportError(e); }
  664.     },
  665.     
  666.     addRetainDomain : function(path)
  667.     {    
  668.         this.retainDomains[ path ] = path;
  669.     },
  670.     
  671.     getRetainDomains : function()
  672.     {
  673.         return this.JSON.encode(this.retainDomains);
  674.     },
  675.     
  676.     
  677.     
  678.     // --- //
  679.     
  680.     fileDownloader : function(url, isBinary, delegate, callback)
  681.     {
  682.         this.url = url;
  683.         this.isBinary = isBinary;
  684.         this.delegate = delegate;
  685.         this.callback = callback;
  686.         
  687.         this.data = "";
  688.     },
  689.     
  690.     fileDownloaderPrototype : function()
  691.     {
  692.         
  693.         this.fileDownloader.prototype =
  694.         {
  695.             
  696.             start : function()
  697.             {
  698.                 //dump("\nstart file: " + Components.classes["@mozilla.org/thread-manager;1"].getService().isMainThread + this.url);
  699.                 try
  700.                 {
  701.                     if (this.url)
  702.                     {
  703.                         try {
  704.                             this.startXMLhttpRequest();
  705.                         } catch(e)
  706.                         {
  707.                            dump("\nfileDownloader Error x1 : " + e);
  708.                              //Components.utils.reportError(e);
  709.                         }
  710.                         
  711.                         return;
  712.                     }
  713.                 } catch(e){
  714.                     dump("\nfileDownloader Error x2 : " + e);
  715.                     //Components.utils.reportError(e);
  716.                 }
  717.                 
  718.                 //else
  719.                 this.finish(false);
  720.                 
  721.             },
  722.                         
  723.             // XMLhttpRequest - used for text pages
  724.             
  725.             startXMLhttpRequest : function()
  726.             {
  727.                 var self = this;
  728.                 this.request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);  
  729.                 this.request.open("GET", this.url, true);        
  730.                 this.request.onreadystatechange = function(e){ self.onReadyStateChange.call(self, e); };
  731.                 
  732.                 if (this.isBinary)
  733.                 {
  734.                     this.request.overrideMimeType('text/plain; charset=x-user-defined');  
  735.                 }
  736.                 
  737.                 this.request.send();
  738.             },
  739.             
  740.             onReadyStateChange : function(e)
  741.             {
  742.                 //dump("\nonReadyStateChange: " + this.request.readyState + ' | '  + this.url);
  743.                 /* - TODO - implement this
  744.                 if (this.request.readyState == 2 && this.request.channel.originalURI.spec != this.request.channel.URI.spec)
  745.                 {
  746.                     if ( this.delegate.dupeCheckAbsolute[ this.request.channel.URI.spec ] )
  747.                     {
  748.                         // would either need to make a copy of the file (assuming its already been downloaded)
  749.                         // or would have to update the source's literal with the new location
  750.                         // in that case it would have to know which source to update (css or markup)
  751.                         // best solution would likely be simlinks
  752.                         this.request.abort();
  753.                         dump("\n dupe aborted");
  754.                     }
  755.                     else
  756.                     {
  757.                         // Add it to the checker
  758.                         this.delegate.dupeCheckAbsolute[ this.request.channel.URI.spec ] = true;
  759.                     }
  760.                 }
  761.                 else*/ if (this.request.readyState == 4)
  762.                 {
  763.                     if (this.request.status == 200)
  764.                     {                                               
  765.                         this.data = this.request.responseText;
  766.                         this.finish(true);               
  767.                     }
  768.                     else
  769.                     {
  770.                         this.finish(false);
  771.                     }
  772.                 }
  773.             },   
  774.             
  775.             // Finish
  776.             
  777.             finish : function(success)
  778.             {
  779.                 //dump("\nfinish: " + this.url);
  780.                 
  781.                 this.finished = true;
  782.                 this.success = success;
  783.                 this.callback.call(this.delegate, this);
  784.             }
  785.             
  786.         }
  787.     }
  788.     
  789.     
  790. };
  791.  
  792. var components = [RILwebDownloader];
  793. function NSGetModule(compMgr, fileSpec) {
  794.   return XPCOMUtils.generateModule(components);
  795. }
  796.